// Copyright 2020 Fuzhou Rockchip Electronics Co., Ltd. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>

#include "rkmedia_api.h"
#include "librtsp/rtsp_demo.h"

#define RTSP_WIDTH		1920
#define RTSP_HEIGHT		1080


#define RKMODULE_MAX_VC_CH 4
struct rkmodule_vc_fmt_info {
	__u32 width[RKMODULE_MAX_VC_CH];
	__u32 height[RKMODULE_MAX_VC_CH];
	__u32 fps[RKMODULE_MAX_VC_CH];
} __attribute__ ((packed));
struct rkmodule_vc_hotplug_info {
	__u8 detect_status;
} __attribute__ ((packed));

#define RKMODULE_GET_VC_FMT_INFO 	_IOR('V', BASE_VIDIOC_PRIVATE + 12, struct rkmodule_vc_fmt_info)
#define RKMODULE_GET_VC_HOTPLUG_INFO 	_IOR('V', BASE_VIDIOC_PRIVATE + 13, struct rkmodule_vc_hotplug_info)

struct ahd_cam_info {
	__u32 width;
	__u32 height;
	__u32 fps;
	__u32 plugged;
	IMAGE_TYPE_E type;
	char *dev;
};

struct ahd_cam_info g_ahd[8] = {
	{1920, 1080, 25, 0, IMAGE_TYPE_NV12, "/dev/video0"},
	{1920, 1080, 25, 0, IMAGE_TYPE_NV12, "/dev/video1"},
	{1920, 1080, 25, 0, IMAGE_TYPE_NV12, "/dev/video2"},
	{1920, 1080, 25, 0, IMAGE_TYPE_NV12, "/dev/video3"},
	{1920, 1080, 25, 0, IMAGE_TYPE_UYVY422, "/dev/video5"},
	{1920, 1080, 25, 0, IMAGE_TYPE_UYVY422, "/dev/video7"},
	{1920, 1080, 25, 0, IMAGE_TYPE_UYVY422, "/dev/video8"},
	{1920, 1080, 25, 0, IMAGE_TYPE_UYVY422, "/dev/video9"},
};
RECT_S area_4x2[8] = {
    {0, 0, RTSP_WIDTH / 4, RTSP_HEIGHT / 2},
    {RTSP_WIDTH / 4, 0, RTSP_WIDTH / 4, RTSP_HEIGHT / 2},
    {RTSP_WIDTH / 2, 0, RTSP_WIDTH / 4, RTSP_HEIGHT / 2},
    {RTSP_WIDTH * 3 / 4, 0, RTSP_WIDTH / 4, RTSP_HEIGHT / 2},
    {0, RTSP_HEIGHT / 2, RTSP_WIDTH / 4, RTSP_HEIGHT / 2},
    {RTSP_WIDTH / 4, RTSP_HEIGHT / 2, RTSP_WIDTH / 4, RTSP_HEIGHT / 2},
    {RTSP_WIDTH / 2, RTSP_HEIGHT / 2, RTSP_WIDTH / 4, RTSP_HEIGHT / 2},
    {RTSP_WIDTH * 3 / 4, RTSP_HEIGHT / 2, RTSP_WIDTH / 4, RTSP_HEIGHT / 2},
};
rtsp_demo_handle g_rtsplive = NULL;
static rtsp_session_handle g_rtsp_session;
static bool quit = false;
static void sigterm_handler(int sig) {
	fprintf(stderr, "signal %d\n", sig);
	quit = true;
}

void video_packet_cb(MEDIA_BUFFER mb) {
	static RK_S32 packet_cnt = 0;
	if (quit)
		return;

//	printf("#Get packet-%d, size %zu, %llu\n", packet_cnt, RK_MPI_MB_GetSize(mb), RK_MPI_MB_GetTimestamp(mb));

	if (g_rtsplive && g_rtsp_session) {
		rtsp_tx_video(g_rtsp_session, RK_MPI_MB_GetPtr(mb), RK_MPI_MB_GetSize(mb), RK_MPI_MB_GetTimestamp(mb));
		rtsp_do_event(g_rtsplive);
	}

	RK_MPI_MB_ReleaseBuffer(mb);
	packet_cnt++;
}

void get_ahd_cam_info(int av_group, struct ahd_cam_info *info)
{
	int fd;
	struct rkmodule_vc_hotplug_info status;
	struct rkmodule_vc_fmt_info fmt;
	int ch = 0;
	
	if (0==av_group) {
		fd = open("/dev/v4l-subdev3", O_RDWR, 0);
	} else {
		fd = open("/dev/v4l-subdev7", O_RDWR, 0);
	}

	ioctl(fd, RKMODULE_GET_VC_FMT_INFO, &fmt);
	ioctl(fd, RKMODULE_GET_VC_HOTPLUG_INFO, &status);
	
	for(ch = 0; ch < 4; ch++) {
		info[ch].width = fmt.width[ch];
		info[ch].height = fmt.height[ch];
		info[ch].fps = fmt.fps[ch];
		info[ch].plugged = (status.detect_status & (1 << ch)) ? 1 : 0;
	}
	close(fd);
}

void dump_ahd_cam_info(struct ahd_cam_info *info)
{
	int ch = 0;
	for(ch = 0; ch < 8; ch++) {
		printf("# AV group:%d ch: %d\n", ch/4, ch%4);
		printf("\t width: %d\n", info[ch].width);
		printf("\t height: %d\n", info[ch].height);
		printf("\t fps: %d\n", info[ch].fps);
		printf("\t plug in: %d\n", info[ch].plugged);
	}
}

int rtsp_enable(void)
{
	g_rtsplive = create_rtsp_demo(554);
	g_rtsp_session = rtsp_new_session(g_rtsplive, "/live/main_stream");
//	rtsp_set_video(g_rtsp_session, RTSP_CODEC_ID_VIDEO_H264, NULL, 0);
	rtsp_set_video(g_rtsp_session, RTSP_CODEC_ID_VIDEO_H265, NULL, 0);
	rtsp_sync_video_ts(g_rtsp_session, rtsp_get_reltime(), rtsp_get_ntptime());
	return 0;
}

void rtsp_disable(void)
{
	rtsp_del_demo(g_rtsplive);
}

int pipeline_enable(void)
{
	int ret = 0;
	VMIX_DEV_INFO_S vmix_dev_info;
	VENC_CHN_ATTR_S venc_chn_attr;

	// create VI
	for (RK_U16 i = 0; i < 8; i++) {
		VI_CHN_ATTR_S vi_chn_attr;
		memset(&vi_chn_attr, 0, sizeof(vi_chn_attr));
		vi_chn_attr.pcVideoNode = g_ahd[i].dev;
		vi_chn_attr.u32BufCnt = 4;
		vi_chn_attr.u32Width = g_ahd[i].width;
		vi_chn_attr.u32Height = g_ahd[i].height;
		vi_chn_attr.enPixFmt = g_ahd[i].type;
		vi_chn_attr.enWorkMode = VI_WORK_MODE_NORMAL;
		vi_chn_attr.enBufType = VI_CHN_BUF_TYPE_MMAP;
		ret = RK_MPI_VI_SetChnAttr(0, i, &vi_chn_attr);
		ret |= RK_MPI_VI_EnableChn(0, i);
		if (ret) {
			printf("Create vi[%d] failed! ret=%d\n", i, ret);
			return -1;
		}
	}
	
	// create VMIX
	memset(&vmix_dev_info, 0, sizeof(vmix_dev_info));
	vmix_dev_info.enImgType = IMAGE_TYPE_NV12;
	vmix_dev_info.u16Fps = 25;
	vmix_dev_info.u32ImgWidth = RTSP_WIDTH;
	vmix_dev_info.u32ImgHeight = RTSP_HEIGHT;
	for (RK_U16 i = 0; i < 8; i++) {
		vmix_dev_info.stChnInfo[i].stInRect.s32X = 0;
		vmix_dev_info.stChnInfo[i].stInRect.s32Y = 0;
		vmix_dev_info.stChnInfo[i].stInRect.u32Width = g_ahd[i].width;
		vmix_dev_info.stChnInfo[i].stInRect.u32Height = g_ahd[i].height;
		vmix_dev_info.stChnInfo[i].stOutRect.s32X = area_4x2[i].s32X;
		vmix_dev_info.stChnInfo[i].stOutRect.s32Y = area_4x2[i].s32Y;
		vmix_dev_info.stChnInfo[i].stOutRect.u32Width = area_4x2[i].u32Width;
		vmix_dev_info.stChnInfo[i].stOutRect.u32Height = area_4x2[i].u32Height;
		printf("#CHN[%d]:IN<0,0,%d,%d> --> Out<%d,%d,%d,%d>\n", i,
			g_ahd[i].width, g_ahd[i].height, 
			area_4x2[i].s32X, area_4x2[i].s32Y, area_4x2[i].u32Width, area_4x2[i].u32Height);
		vmix_dev_info.u16ChnCnt++;
	}
	ret = RK_MPI_VMIX_CreateDev(0, &vmix_dev_info);
	if (ret) {
		printf("Init VMIX device failed! ret=%d\n", ret);
		return -1;
	}
	for (RK_U16 i = 0; i < 8; i++) {
		ret = RK_MPI_VMIX_EnableChn(0, i);
		if (ret) {
			printf("Enable VM[0]:Chn[%d] failed! ret=%d\n", i, ret);
			return -1;
		}
	}
	
	// create VENC
	memset(&venc_chn_attr, 0, sizeof(venc_chn_attr));
//	venc_chn_attr.stVencAttr.enType = RK_CODEC_TYPE_H264;
//	venc_chn_attr.stRcAttr.enRcMode = VENC_RC_MODE_H264CBR;
//	venc_chn_attr.stRcAttr.stH264Cbr.u32Gop = 25;
//	venc_chn_attr.stRcAttr.stH264Cbr.u32BitRate = RTSP_WIDTH * RTSP_HEIGHT;
//	venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateDen = 1;
//	venc_chn_attr.stRcAttr.stH264Cbr.fr32DstFrameRateNum = 25;
//	venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateDen = 1;
//	venc_chn_attr.stRcAttr.stH264Cbr.u32SrcFrameRateNum = 25;
    venc_chn_attr.stVencAttr.enType = RK_CODEC_TYPE_H265;
    venc_chn_attr.stRcAttr.enRcMode = VENC_RC_MODE_H265CBR;
    venc_chn_attr.stRcAttr.stH265Cbr.u32Gop = 25;
    venc_chn_attr.stRcAttr.stH265Cbr.u32BitRate = RTSP_WIDTH * RTSP_HEIGHT;
    venc_chn_attr.stRcAttr.stH265Cbr.fr32DstFrameRateDen = 1;
    venc_chn_attr.stRcAttr.stH265Cbr.fr32DstFrameRateNum = 25;
    venc_chn_attr.stRcAttr.stH265Cbr.u32SrcFrameRateDen = 1;
    venc_chn_attr.stRcAttr.stH265Cbr.u32SrcFrameRateNum = 25;
	venc_chn_attr.stVencAttr.imageType = IMAGE_TYPE_NV12;
	venc_chn_attr.stVencAttr.u32PicWidth = RTSP_WIDTH;
	venc_chn_attr.stVencAttr.u32PicHeight = RTSP_HEIGHT;
	venc_chn_attr.stVencAttr.u32VirWidth = RTSP_WIDTH;
	venc_chn_attr.stVencAttr.u32VirHeight = RTSP_HEIGHT;
	venc_chn_attr.stVencAttr.u32Profile = 77;
	ret = RK_MPI_VENC_CreateChn(0, &venc_chn_attr);
	if (ret) {
		printf("ERROR: create VENC[0] error! ret=%d\n", ret);
		return -1;
	}
	MPP_CHN_S stEncChn;
	stEncChn.enModId = RK_ID_VENC;
	stEncChn.s32DevId = 0;
	stEncChn.s32ChnId = 0;
	ret = RK_MPI_SYS_RegisterOutCb(&stEncChn, video_packet_cb);
	if (ret) {
		printf("ERROR: register output callback for VENC[0] error! ret=%d\n", ret);
		return -1;
	}
	
	// bind the pipeline
	MPP_CHN_S stSrcChn = {0};
	MPP_CHN_S stDestChn = {0};
	printf("#Bind VMX[0] to VENC[0]....\n");
	stSrcChn.enModId = RK_ID_VMIX;
	stSrcChn.s32DevId = 0;
	stSrcChn.s32ChnId = 0; // invalid
	stDestChn.enModId = RK_ID_VENC;
	stDestChn.s32DevId = 0;
	stDestChn.s32ChnId = 0;
	ret = RK_MPI_SYS_Bind(&stSrcChn, &stDestChn);
	if (ret) {
		printf("Bind VMX[0] to VENC[0] failed! ret=%d\n", ret);
		return -1;
	}
	for (RK_U16 i = 0; i < 8; i++) {
		printf("#Bind VI[%u] to VM[0]:Chn[%u]....\n", i, i);
		stSrcChn.enModId = RK_ID_VI;
		stSrcChn.s32DevId = 0;
		stSrcChn.s32ChnId = i;
		stDestChn.enModId = RK_ID_VMIX;
		stDestChn.s32DevId = 0;
		stDestChn.s32ChnId = i;
		ret = RK_MPI_SYS_Bind(&stSrcChn, &stDestChn);
		if (ret) {
			printf("Bind vi[%d] to vmix[0]:Chn[%u] failed! ret=%d\n", i, i, ret);
			return -1;
		}
	}
	
	return 0;
}

int pipeline_disable(void)
{
	int ret = 0;
	MPP_CHN_S stSrcChn = {0};
	MPP_CHN_S stDestChn = {0};
	
	printf("#UnBind VMX[0] to VENC[0]....\n");
	stSrcChn.enModId = RK_ID_VMIX;
	stSrcChn.s32DevId = 0;
	stSrcChn.s32ChnId = 0; // invalid
	stDestChn.enModId = RK_ID_VENC;
	stDestChn.s32DevId = 0;
	stDestChn.s32ChnId = 0;
	ret = RK_MPI_SYS_UnBind(&stSrcChn, &stDestChn);
	if (ret) {
		printf("UnBind VMX[0] to VENC[0] failed! ret=%d\n", ret);
	}

	for (RK_U16 i = 0; i < 8; i++) {
		printf("#UnBind VI[%d] to VM[0]:Chn[%u]....\n", i, i);
		stSrcChn.enModId = RK_ID_VI;
		stSrcChn.s32DevId = 0;
		stSrcChn.s32ChnId = i;
		stDestChn.enModId = RK_ID_VMIX;
		stDestChn.s32DevId = 0;
		stDestChn.s32ChnId = i;
		ret = RK_MPI_SYS_UnBind(&stSrcChn, &stDestChn);
		if (ret) {
			printf("UnBind vi[%d] to vmix[0]:Chn[%u] failed! ret=%d\n", i, i, ret);
		}
	}
	
	RK_MPI_VENC_DestroyChn(0);
	for (RK_U16 i = 0; i < 8; i++) {
		RK_MPI_VMIX_DisableChn(0, i);
	}
	RK_MPI_VMIX_DestroyDev(0);
	for (RK_U16 i = 0; i < 8; i++) {
		RK_MPI_VI_DisableChn(0, i);
	}

	return 0;
}

int main(int argc, char *argv[])
{
	int ch;
	int w,h;
	int plugged;

	get_ahd_cam_info(0, &g_ahd[0]);
	get_ahd_cam_info(1, &g_ahd[4]);

//	dump_ahd_cam_info(g_ahd);

	// check validation
	w=g_ahd[4].width;
	h=g_ahd[4].height;
	plugged = g_ahd[4].plugged;
	if (0==plugged) {
		for (ch=5; ch<8; ch++) {
			if (0!=g_ahd[ch].plugged) {
				dump_ahd_cam_info(g_ahd);
				printf("## error: AV2 group should use AV2_1 channel first\n");
				return -1;
			}
		}
	}
	for (ch=5; ch<8; ch++) {
		if ( (g_ahd[4].width!=g_ahd[ch].width) ||
				(g_ahd[4].height!=g_ahd[ch].height) ) {
			dump_ahd_cam_info(g_ahd);
			printf("## error: every channel in AV2 group should be the same resolution\n");
			return -1;
		}
	}
	
	rtsp_enable();
	
	RK_MPI_SYS_Init();

	pipeline_enable();
	
	while (!quit) {
		usleep(500000);
	}
	
	pipeline_disable();
	rtsp_disable();
	
	return 0;
}
